/*
 * Copyright (c) 2010-2013 Ruben Chakhmakhchyan. All rights reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Eclipse Public License
 * Version 1.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/EPL-1.0 and read it before using this file.
 */

#pragma once

#include "plugin.h"
#include "plugin_model.h"
#include "threading.h"
#include "message_listener.h"
#include "image_acquisition_model.h"
#include "image_site.h"
#include "list.h"


enum class DriverModelType : int
{
    Default = 0,
    Twain = 1,
    Wia = 2
};


class _PluginModel : public PluginModel, public MessageListener, public ImageSite
{
#ifdef _WINDOWS

    typedef POINT Point;

#else

    typedef struct Point
    {
        long x;
        long y;
    } Point;

#endif

    typedef struct Size
    {
        long width;
        long height;
    } Size;

    typedef struct Rect
    {
        union
        {
            struct
            {
                long left, top;
            };
            Point pos;
        };
        union
        {
            struct
            {
                long width, height;
            };
            Size size;
        };

        long getRight() { return left + width; }
        long getBottom() { return top + height; }
    } Rect;

private:

    class Image : public PluginImage
    {
    public:

        enum class PackingAction { Store, Extract, Lighten };

    public:

        Image( PluginModel* model, Pixels& pixels );

        ~Image();

        void store();
        void lighten();
        void extract();
        void pack( PackingAction action );

        bool isStored() { return m_storedData != NULL; }
        bool isExtracted() { return m_pixels.pixels != NULL; }

        void* getData() { return m_pixels.pixels; }
        const Pixels* getPixels() { return &m_pixels; }
        uint_t getDataLength() { return m_pixels.length; }
        void* getStoredData() { return m_storedData; }
        uint_t getStoredDataLength() { return m_storedDataLength; }
        StoreFormat getStoredDataFormat() { return m_storedDataFormat; }

        virtual long getWidth() const { return m_pixels.width; }
        virtual long getHeight() const { return m_pixels.height; }

        virtual long getResolution() const { return m_pixels.resolution; }

        virtual PixelType getPixelType() const;

        virtual void* getTag() const { return m_tag; }
        virtual void setTag( void* value ) { m_tag = value; }

        virtual void setEventHandler( DisposeEventHandler* value ) { m_eventHandler = value; }

        const byte_t getBitsPerChannel() { return m_pixels.bitsPerChannel; }

        const byte_t getBitCount() { return m_pixels.bitsPerPixel; }

        bool getPaletteInverted() { return m_pixels.paletteInverted; }

        const ImageProps* getProperties() const { return NULL; }

        bool isLocked();
        void doLock();
        void lock(); // external lock
        void unlock();

        void activate() { m_isActive = true; }
        void deactivate() { m_isActive = false; }

        virtual int getIndex() const { return m_index; }
        void setIndex( int value ) { m_index = value; }

        byte_t* getBits() const;

        virtual const char* getThumbnailUrl();
        virtual bool getImageData( List<byte_t>& list, int x, int y, int width, int height );
        virtual uint32_t getPixel( int x, int y );
        virtual void setPixel( int x, int y, uint32_t value );

        void compress();

    private:

        Pixels m_pixels; // pixels

        // image stored in specified format
        byte_t* m_storedData;
        uint_t m_storedDataLength;

        // image thumbnail (JPEG) data url
        char* m_thumbnailUrl;

        volatile bool m_storing, m_extracting, m_locked;
        //volatile int m_lockCount;

        HANDLE m_hLockEvent;
        volatile bool m_isLockInternal, m_isLockExternal;

        bool m_packed;

        volatile bool m_isActive;

        int m_index;

        PluginModel* m_model;

        StoreFormat m_storedDataFormat;

        void* m_tag;
        DisposeEventHandler* m_eventHandler;
    };

public:

    _PluginModel( Plugin* pPlugin, MessageListener* messageListener );
    ~_PluginModel();

    // PluginModel interface implementation
    virtual bool getPlatformSupported() { return m_osMajorVersion > 5; }
    virtual Plugin* getPlugin() { return m_pPlugin; }
    virtual ResultCode scan( bool add, int& numberOfImages );
#ifndef _ADDON
    virtual void upload();
#endif
    virtual bool hasImage() { return m_activeImage != NULL; }
    virtual void render( HDC hdc );
    virtual void resize( size_t width, size_t height );
    virtual void requestUpdateView() { m_pPlugin->updateView(); }
    virtual bool saveToFile( wchar_t* path, DocumentFormat format, int indices[], int count );
    virtual bool stringToDocumentFormat( const wchar_t* string, DocumentFormat& format );
    virtual bool stringToDocumentFormat( const char* string, DocumentFormat& format );
    virtual int getSelectedImageIndex() { return m_activeImageIndex; }
    virtual void selectImage( int index, bool refreshIfActive = false );
    virtual void selectNextImage();
    virtual void selectPreviousImage();
    virtual int getImageCount() { return m_images.size(); }
    virtual bool connect();
    virtual void disconnect();
    virtual bool getIsConnected() { return m_iaModel && m_iaModel->getIsConnected(); }
    virtual void clearImages();

    void setViewport();

    // asynchronous
#ifndef _ADDON
    virtual bool beginUpload( DocumentFormat format, char* url, List<char*>* filenames, int indices[], int count, AsyncCallback pAsyncCallback, int id );
#endif
    virtual bool beginSave( DocumentFormat format, bool multipage, wchar_t* path, List<wchar_t*>* filenames, int indices[], int indexCount, AsyncCallback pAsyncCallback, int id );
    virtual bool beginScan( bool add, AsyncCallback pAsyncCallback );

    // tools
    bool m_snapping;

    // zoom
    virtual bool getZoomState() { return m_zoomState; }
    virtual void setZoomState( bool value, bool isInternal = true );
    virtual double getZoom() { return m_zoomFactor; }
    virtual void setZoom( double factor );
    virtual double getZoomStep() { return 0.250F; }
    virtual void setZoomStep( double step ) { }
    virtual double getZoomFit() { return m_zoomFit; }
    virtual void resetZoom();
    // pan
    virtual bool getPanState() { return m_panState; }
    virtual void setPanState( bool value, bool isInternal = true );
    virtual bool panX( int delta, bool updateView = true );
    virtual bool panY( int delta, bool updateView = true );
    virtual bool pan( int dx, int dy );
    // rotate
    virtual bool rotate( int degrees );
    // crop
    virtual bool getCropState() { return m_cropState; }
    virtual void setCropState( bool value, bool isInternal = true );
    virtual bool crop();
    virtual bool crop( int left, int top, int width, int height );

    virtual void mouseLeftDown( short xpos, short ypos );
    virtual void mouseLeftUp( short xpos, short ypos );
    virtual void mouseMove( short xpos, short ypos );
    virtual bool keyDown( uint_t key );
    virtual bool keyUp( uint_t key );

    void startMousePan( long xpos, long ypos );
    void stopMousePan();
    void doMousePan( long xpos, long ypos );

    void startMouseCrop( long xpos, long ypos );
    void stopMouseCrop( long xpos, long ypos );
    void doMouseCrop( long xpos, long ypos );
    void resetCrop();

    void startMouseZoom( long xpos, long ypos );
    void stopMouseZoom( long xpos, long ypos );
    void doMouseZoom( long xpos, long ypos );

    void setZoomFactor( double factor );
    void updateBounds();
    void alignMousePos( long& x, long& y );
    void drawCropRect( HDC hdc, Point& p, Size& s );
    bool pointInRect( Rect& r, int x, int y, int threshold );
    int pointOnSide( Rect& r, int x, int y, int threshold );
    int pointInCorner( Rect& r, int x, int y, int threshold );

    virtual InteractiveTool getCurrentTool();

    bool applyDriverModel();

    virtual ImageAcquisitionModel* getIaModel();

    size_t saveToStream( void** pdata, DocumentFormat format, int indices[], int count );
    size_t saveToFile( DocumentFormat format, bool multipage, int indices[], int count, List<wchar_t*>* filenames );

    ImageList* getImageList( DocumentFormat format, int indices[], int count );

    // icaps
    virtual PixelType getPixelType() const;
    virtual void setPixelType( const PixelType value );
    virtual int getResolution() const;
    virtual void setResolution( const int value );
    virtual int getContrast() const;
    virtual void setContrast( const int value );

    // caps
    virtual TransferMode getTransferMode() const { return m_transferMode; }
    virtual void setTransferMode( TransferMode value ) { m_transferMode = value; }
    virtual PaperSource getPaperSource() { return m_paperSource; }
    virtual void setPaperSource( PaperSource value );
    virtual bool getDuplex();
    virtual void setDuplex( bool value );

    virtual int getDriverModel() const { return m_iaModel->getModelTypeId(); }
    virtual bool setDriverModel( int value );
    virtual bool getDevices();
    virtual ImagingDevice* getDevice( int id );
    virtual int getDeviceCount();
    virtual bool getDevicesRefreshed() { return m_devicesRefreshed; }
    virtual void setDevicesRefreshed( bool value ) { m_devicesRefreshed = value; }
    virtual bool getShowDeviceUI() { return m_showDeviceUI; }
    virtual void setShowDeviceUI( bool value ) { m_showDeviceUI = value; }
    virtual int getDeviceIndex();
    virtual void setDeviceIndex( int index );

    virtual uint_t getImageQuality() { return m_imageQuality; }
    virtual void setImageQuality( uint_t value ) { if ( value > 0 && value <= 100 ) m_imageQuality = value; }

    virtual void setParentWindow( void* handle ) { m_parentWindow = ( HWND ) handle; }

    virtual uint_t getImageThumbnailSize() const { return m_thumbnailSize; }
    virtual void setImageThumbnailSize( uint_t value ) { if ( m_thumbnailSize >= 64 && m_thumbnailSize <= 256 ) m_thumbnailSize = value; }

    virtual const char* getImageThumbnailUrl( int index ) const;

    virtual const char* getVersion() const;

    virtual ImageFilter getFilter( DocumentFormat format, PixelType pixelType ) const;
    virtual ImageFilter getFilter( DocumentFormat format, uint_t bitsPerPixel ) const;
    virtual void setFilter( DocumentFormat format, PixelType pixelType, ImageFilter filter );

    virtual bool getAutoPosition() { return m_autoPosition; }
    virtual void setAutoPosition( bool value ) { m_autoPosition = value; }

    virtual bool parseIndices( const char* rangeString, int*& indices, int& count );

    virtual bool getAutoCompress() { return m_autoCompress; }
    virtual void setAutoCompress( bool value ) { m_autoCompress = value; }

    virtual bool getAutoPause() { return m_autoPause; }
    virtual void setAutoPause( bool value ) { m_autoPause = value; }

    virtual int getAutoCompressionLevel() { return m_autoCompressionLevel; }
    virtual void setAutoCompressionLevel( int value ) { m_autoCompressionLevel = value; }

    virtual bool getAutoCompressInBackground() { return m_backgroundCompression; }
    virtual void setAutoCompressInBackground( bool value ) { m_backgroundCompression = value; }

    virtual void resumeScan( bool proceed );
    virtual void pauseScan();
    virtual void stopScan();

    virtual StoreFormat getStoreFormat() { return m_storeFormat; }
    virtual void setStoreFormat( StoreFormat value ) { m_storeFormat = value; }

    virtual void initializeDevice();

    virtual bool deleteImage( int index );
    virtual bool shiftImage( int direction );
    virtual bool moveImage( int index ) { return false; }

    virtual double getPageWidth() const { if ( m_iaModel ) return m_iaModel->getPageWidth(); return 0; }
    virtual void setPageWidth( double value ) { if ( m_iaModel ) m_iaModel->setPageWidth( value ); };

    virtual double getPageHeight() const { if ( m_iaModel ) return m_iaModel->getPageWidth(); return 0; }
    virtual void setPageHeight( double value ) { if ( m_iaModel ) m_iaModel->setPageHeight( value ); }

    virtual const PluginImage* getImage( int index ) { return m_images[ index ]; }

    PBITMAPINFOHEADER createDIB( const Pixels& p );

    // MessageClient interface implementation
    virtual void onMessage( char* msg ) { }
    virtual void onError( ResultCode value ) { m_messageListener->onError( value ); }
    virtual void onDeviceConnection( bool state ) { m_messageListener->onDeviceConnection( state ); }

    // ImageSite interface implementation
    virtual bool createPixels( void* rawImage, Pixels& pixels );
    virtual bool imageReady( Pixels& image );
    virtual void imageReadyCallback( Pixels& image );
    virtual bool getScanStop() { return m_cancelScan != 0; }
    virtual void setScanStop( bool value ) { ::InterlockedExchange( &m_cancelScan, value ? 1 : 0 ); }

    virtual void setImage( Pixels& pImage, bool add = true );

    //void updateView();
    void renderBuffer( HDC hdc = NULL );

    virtual void getViewSize( int& width, int& height );
    virtual void setViewSize( const int width, const int height );

    HWND getMessageWindow();
    HANDLE getScanEvent() const { return m_hScanEvent; }
    HANDLE getCompressEvent() const { return m_hCompressEvent; }
    bool getCompressionComplete() const { return m_compressionComplete != 0; }
    //bool getCompressionIndex() const { return m_compressionIndex; }
    List<uint_t>& getCompressionQueue() { return m_compressionQueue; }

    void compressImage( uint_t index );
    void postCallback( AsyncInvokeArg* arg, AsyncCallback callback );

    void lockImages( bool lock );

private:

    Plugin* m_pPlugin;
    MessageListener* m_messageListener;

    void *m_pScanThreadHandle, *m_pSaveThreadHandle;
#ifndef _ADDON
    void *m_pUploadThreadHandle;
#endif

    HDC m_hdc;

    bool m_refresh;
    HBITMAP m_hRenderBitmap;
    byte_t* m_renderDib;
    size_t m_renderDibSize;

    Rect m_viewPortionRect; // the portion (in scaled coordinates) of the zoomed image displayed in the view port
    // the position of the portion area is an offset from left top (0, 0) corner of the scaled image
    Point m_viewPortionPos; // the position of the zoomed portion in the view port
    Size m_viewSize; // the size of the zoomed image

    Size m_viewportSize; // size of the view port (device context)
    Size m_imageSize; // the full size of the image (1:1)

    Image* m_activeImage;
    int m_activeImageIndex;
    List<Image*> m_images;

    uint_t m_imageQuality;

    // device capabilities
    TransferMode m_transferMode;
    PaperSource m_paperSource;

    // driver
    ImageAcquisitionModel* m_iaModel;
    bool m_connected;
    DriverModelType m_driverModelType;

    // devices
    bool m_devicesRefreshed;
    bool m_showDeviceUI;
    unsigned int m_deviceIndex;

    // tools
    Point m_mouseStartPos;						// starting mouse position
    Point m_mousePos;							// current mouse position
    Point m_mouseTopPos;						// top mouse position

    // zoom
    bool m_zoomState;							// true if zoom tool is on
    double m_zoomFactor;						// equals 1.0F for the actual size
    double m_inverseZoomFactor;					// (1 / m_zoomFactor)
    bool m_zoomFit;								// indicates if the image fits the context
    double m_fitZoom;							// zoom value in case when image fits the context
    double m_dZoomFactor;
    bool m_mouseZoom, m_mouseZooming;
    bool m_mouseRectInitialized;				// whether the rubber band has been drawn at least once

    // pan
    bool m_panState;							// true if pan tool is on. m_imageLeft and m_imageTop indicate the current position
    bool m_mousePan;							// identifies whether the tool is enabled
    bool m_spacePressed;						// identified whether the space key is pressed

    // crop
    bool m_cropState;							// indicates whether the crop tool is enabled
    bool m_mouseCrop;							// indicates whether the tool is working
    bool m_mouseCropInitialized;
    Size m_cropRectSize;						// size of the crop rect
    Rect m_cropRect;							// the real (1:1) crop rect relative to image rect
    Point m_cropRectViewPos;					// position of the crop rect relative to image-view coordinates
    Point m_cropRectWindowPos;					// position of the crop rect relative to window coordinates
    bool m_cropRectDefined;
    bool m_mouseCropDragging;
    bool m_mouseCropRefine;
    int m_cropSide;
    int m_cropCorner;
    Point m_cropRectStartPos;

    bool m_updateBoundsResetPosition;

    HWND m_parentWindow;

    ImageFilter m_pdfFilters[ 3 ];

    bool m_autoPosition;
    bool m_autoCompress;
    bool m_backgroundCompression;
    bool m_autoPause;
    int m_autoCompressionLevel;

    HANDLE m_hScanEvent;
    volatile unsigned int m_cancelScan, m_pauseScan;

    HANDLE m_hImagesLockEvent;
    volatile bool m_imagesLocked, m_imagesLockWaiting;

    HANDLE m_hCompressEvent;
    volatile unsigned int m_compressionComplete;

    List<uint_t> m_compressionQueue;

    StoreFormat m_storeFormat;

    uint_t m_thumbnailSize;

    volatile long m_numberOfImagesScanned;

    static bool m_static; // determines whether the static data has been initialized
    static uint_t m_osMajorVersion;
};
